from django.db import models
from django.utils.functional import curry
from django.db.models import IntegerField
from django.db.models import signals
import time
from django.db.models.fields import AutoField
from django.db import connection, transaction, DatabaseError

class RecordModifiedError(DatabaseError):
    pass

class VersionField(IntegerField):
    def __init__(self, verbose_name=None, name=None, help_text='', 
                 db_column=None, db_tablespace=None):
        
        super(VersionField,self).__init__(verbose_name, name, editable=False,
                                          help_text=help_text, 
                                          db_tablespace=db_tablespace, db_column=db_column )

        
    def contribute_to_class(self, cls, name):
        super(VersionField,self).contribute_to_class(cls, name)
        cls.RevisionMetaInfo.field = self        
        setattr(cls, '_get_revision_number', curry(cls._get_REVISION_NUMBER, field=self))
        setattr(cls, '_set_revision_number', curry(cls._set_REVISION_NUMBER, field=self))
        
    def get_default(self):
        return 0 #int(time.time())
 
class ConcurrentModelManager(models.Manager): 
    def get_or_create(self, **kwargs):
        if 'pk' in kwargs:
            pass
        else:
            super(ConcurrentModelManager).get_or_create(**kwargs)
    
class BaseConcurrentModel(models.Model):
    

    def _get_REVISION_NUMBER(self, field):
        value = getattr(self, field.attname)
        return value

    def _set_REVISION_NUMBER(self, value, field):
        setattr(self,  field.attname, value)
    
            
    class Meta:
       abstract=True
    
    class RevisionMetaInfo:
       field = None
    
    @classmethod
    def get_or_create(cls, pk, *args, **kwargs):
        try:
            return cls.__class__.objects.get(pk=pk)
        except:
            n = cls(pk=pk, *args, **kwargs).save(force_insert=True)            
            return n
    
    def save_base(self, raw=False, cls=None, origin=None,
            force_insert=False, force_update=False):
        """
        Does the heavy-lifting involved in saving. Subclasses shouldn't need to
        override this method. It's separate from save() in order to hide the
        need for overrides of save() to pass around internal-only parameters
        ('raw', 'cls', and 'origin').
        """
        assert not (force_insert and force_update)
        if cls is None:
            cls = self.__class__
            meta = cls._meta #IGNORE:E1101
            if not meta.proxy:
                origin = cls
        else:
            meta = cls._meta

        if origin:
            signals.pre_save.send(sender=origin, instance=self, raw=raw)

        # If we are in a raw save, save the object exactly as presented.
        # That means that we don't try to be smart about saving attributes
        # that might have come from the parent class - we just save the
        # attributes we have been given to the class we have been given.
        # We also go through this process to defer the save of proxy objects
        # to their actual underlying model.
        if not raw or meta.proxy:
            if meta.proxy:
                org = cls
            else:
                org = None
            for parent, field in meta.parents.items():
                # At this point, parent's primary key field may be unknown
                # (for example, from administration form which doesn't fill
                # this field). If so, fill it.
                if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname))

                self.save_base(cls=parent, origin=org)

                if field:
                    setattr(self, field.attname, self._get_pk_val(parent._meta))
            if meta.proxy:
                return
        
        versionField = meta.get_field( cls.RevisionMetaInfo.field.name )
        newVersion = oldVersion = self._get_revision_number()

        if not meta.proxy:
            #non_pks = [f for f in meta.local_fields if not (f.primary_key or f==versionField)]
            non_pks = [f for f in meta.local_fields if not (f.primary_key)]
            #print 1111111111111, self, non_pks
            # First, try an UPDATE. If that doesn't update anything, do an INSERT.
            pk_val = self._get_pk_val(meta)
            pk_set = pk_val is not None
            record_exists = True
            manager = cls._base_manager #IGNORE:E1103
 
            if versionField in meta.local_fields:
                newVersion = max(oldVersion+1, int( time.time()) )
#                force_update = True
                
                #logging.debug("oldVersion:%s newVersion:%s" % (oldVersion, newVersion) )
            
            #logging.debug("%s pk:%-5s old:%s new:%s " % (cls.__name__, pk_val, oldVersion, newVersion) )                
            if pk_set:
                # Determine whether a record with the primary key already exists.
                if (force_update or (not force_insert and
                        manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
                    
                    # It does already exist, so do an UPDATE.
                    if force_update or non_pks:
                        #values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
                        values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks if not isinstance(f, VersionField)]

                        #rows = manager.filter(pk=pk_val)._update(values)
                        
                        if meta.parents == {}:
                            values.append([versionField, None, newVersion])
                            d = {"pk": pk_val,
                                 cls.RevisionMetaInfo.field.name: self._get_revision_number() }
                            rows = manager.filter(**d)._update(values)
                        else:                    
                            rows = manager.filter(pk=pk_val)._update(values)
                                                        
                        if rows == 0:
                            rows = manager.filter(pk=pk_val)
                            if rows:
                                raise RecordModifiedError("Record changed....")                        
                        else:
                            #self.version = newVersion
                            self._set_revision_number( newVersion )
                        if force_update and not rows:
                            raise DatabaseError("Forced update did not affect any rows.")
                else:
                    record_exists = False
            
            if not pk_set or not record_exists:
                if not pk_set:
                    if force_update:
                        raise ValueError("Cannot force an update in save() with no primary key.")
                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, (AutoField, VersionField) )]
                else:
                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, VersionField)]

                if meta.order_with_respect_to:
                    field = meta.order_with_respect_to
                    values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
                record_exists = False
                
                if versionField in meta.local_fields:
                    values.append([versionField, newVersion])
                
                update_pk = bool(meta.has_auto_field and not pk_set)
                if values:
                    # Create a new record.
                    result = manager._insert(values, return_id=update_pk)
                else:
                    # Create a new record with defaults for everything.
                    result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
                self._set_revision_number( newVersion )
                
            
            
                if update_pk:
                    setattr(self, meta.pk.attname, result)
                
            
                 
            transaction.commit_unless_managed()

        if origin:
            signals.post_save.send(sender=origin, instance=self,
                created=(not record_exists), raw=raw)

    save_base.alters_data = True


